//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#pragma once

#include "Graphics/GLshell/Shader.h"
#include "Graphics/Resources/Program.h"

namespace Engine
{
namespace Graphics
{
	
	struct EDefaultShader
	{
		enum type
		{
			NONE,
			COLORED,
			COLOR_WITH_TEXCOORDS,			// without reading texture
			TEXTURED,
			TEXTURED_ALPHA_TEST,
			COLORED_BLEND_WITH_TEXTURE,
			COLORED_WITH_OPACITY_MAP,		// for fonts
			_COUNT
		};
	};



	//
	// Shader Manager
	//

	class ShaderManager : public BaseObject
	{
	// types
	private:
		typedef static_array< ProgramPtr, EDefaultShader::_COUNT >	prog_array_t;


	// variables
	private:
		prog_array_t				_defaultPrograms;

		gl_shader_precission::type	_fragmentFP,
									_fragmentIP,
									_vertexFP,
									_vertexIP;


	// methods
	public:
		ShaderManager (const EngineSubSystemsRef ess);

		// precission
		void SetFragmentShaderPrecission (gl_shader_precission::type fp, gl_shader_precission::type ip);
		void SetVertexShaderPrecission (gl_shader_precission::type fp, gl_shader_precission::type ip);

		// shader compiler
		bool CreateShader (Shader &sh, uni_c_string src);

		// attribs
		void SetProgramAttribs (const ProgramPtr &prog, const GLVertexAttribsInfo &attribs);
		
		// default shaders
		ProgramPtr	GetDefaultProgram (EDefaultShader::type type);
		void		DeleteDefaultPrograms ();

	private:
		void _CreateDefaultProgram (EDefaultShader::type type);
	};
	

	
/*
=================================================
	constructor
=================================================
*/
	inline ShaderManager::ShaderManager (const EngineSubSystemsRef ess) :
		BaseObject(ess),
		_fragmentFP(gl_shader_precission::HIGH), _fragmentIP(gl_shader_precission::HIGH),
		_vertexFP(gl_shader_precission::MEDIUM), _vertexIP(gl_shader_precission::MEDIUM)
	{
	}
		
/*
=================================================
	SetFragmentShaderPrecission
=================================================
*/
	inline void ShaderManager::SetFragmentShaderPrecission (gl_shader_precission::type fp, gl_shader_precission::type ip)
	{
		_fragmentFP	= fp;
		_fragmentIP	= ip;
	}
		
/*
=================================================
	SetVertexShaderPrecission
=================================================
*/
	inline void ShaderManager::SetVertexShaderPrecission (gl_shader_precission::type fp, gl_shader_precission::type ip)
	{
		_vertexFP	= fp;
		_vertexIP	= ip;
	}

/*
=================================================
	CreateShader
=================================================
*/
	inline bool ShaderManager::CreateShader (Shader &sh, uni_c_string src)
	{
		CHECK_ERR( not src.Empty() );

		enum
		{
			HEADER,
			FLOAT_PRECISSION,
			INT_PRECISSION,
			INPUT_SOURCE,
			_COUNT
		};
		
		static const char						empty_str[] = "";
		static const char	*					float_precissions[] = {	"precision highp	float;\n",
																		"precision mediump	float;\n",
																		"precision lowp		float;\n",
																		"" };
		static const char	*					int_precissions[] = {	"precision highp	int;\n",
																		"precision mediump	int;\n",
																		"precision lowp		int;\n",
																		"" };

		static_array< const char *, _COUNT >	source( empty_str );
		const gl_shader::type					type = sh.ShaderType();
		const bool								is_frag = type == gl_shader::FRAGMENT;
		

#		ifdef GLES_EMULATE

			// OpenGL 4.x
			if ( gles::GLES_GetVersion() >= 400 )
			{
				static const char	fs_src[] =	"#version 400 compatibility\n"
												"#define varying in\n"
												"out vec4 gl_FragColor;\n";

				static const char	vs_src[] =	"#version 400 compatibility\n"
												"#define varying out\n"
												"#define attribute in\n";

				source[ HEADER ]			= ( is_frag ? fs_src : vs_src );
				source[ FLOAT_PRECISSION ]	= float_precissions[ (is_frag ? _fragmentFP : _vertexFP) & 3 ];
				source[ INT_PRECISSION ]	= int_precissions[ (is_frag ? _fragmentIP : _vertexIP) & 3 ];
			}
			else
			{
				// do nothing
			}

#		else

			// OpenGL ES 2.0 is default
			source[ FLOAT_PRECISSION ]	= float_precissions[ (is_frag ? _fragmentFP : _vertexFP) & 3 ];
			source[ INT_PRECISSION ]	= int_precissions[ (is_frag ? _fragmentIP : _vertexIP) & 3 ];

#		endif

		source[ INPUT_SOURCE ] = src.cstr();

		CHECK_ERR( sh.SetSource( source.Count(), source.ptr() ) );
		
		if ( not sh.Compile() )
		{
			static const char	frag_failed[] = "fragment shader compilation failed!";
			static const char	vert_failed[] = "vertex shader compilation failed!";

			sh.ShowLog();
			RETURN_ERR( ( is_frag ? frag_failed : vert_failed ) );
		}
		return true;
	}
	
/*
=================================================
	SetProgramAttribs
=================================================
*/
	inline void ShaderManager::SetProgramAttribs (const ProgramPtr &prog, const GLVertexAttribsInfo &attribs)
	{
		CHECK_ERR( prog.IsNotNull(), void() );

		prog->_attribs = attribs;
	}
	
/*
=================================================
	GetDefaultProgram
=================================================
*/
	inline ProgramPtr  ShaderManager::GetDefaultProgram (EDefaultShader::type type)
	{
		if ( _defaultPrograms[ type ].IsNull() )
			_CreateDefaultProgram( type );

		return _defaultPrograms[ type ];
	}
	
/*
=================================================
	DeleteDefaultPrograms
=================================================
*/
	inline void  ShaderManager::DeleteDefaultPrograms ()
	{
		_defaultPrograms.Clear();
	}

/*
=================================================
	_CreateDefaultProgram
=================================================
*/
	inline void ShaderManager::_CreateDefaultProgram (EDefaultShader::type type)
	{
		// fragment shaders
		static const char	fs_colored[] =
				"varying vec4 vColor;\n"
				"\n"
				"void main()\n"
				"{\n"
				"	gl_FragColor = vColor;\n"
				"}";

		static const char	fs_textured[] =
				"varying vec2 vTexCoord;\n"
				"\n"
				"uniform sampler2D unTexture;\n"
				"\n"
				"void main()\n"
				"{\n"
				"	gl_FragColor = texture2D( unTexture, vTexCoord );\n"
				"}";

		static const char	fs_colored_with_opacity_map[] =
				"varying vec2 vTexCoord;\n"
				"varying vec4 vColor;\n"
				"\n"
				"uniform sampler2D unTexture;\n"
				"\n"
				"void main()\n"
				"{\n"
				"	gl_FragColor = vColor * texture2D( unTexture, vTexCoord ).a;\n"
				"}";

		static const char	fs_colored_blend_with_texture[] =
				"varying vec2 vTexCoord;\n"
				"varying vec4 vColor;\n"
				"\n"
				"uniform sampler2D unTexture;\n"
				"\n"
				"void main()\n"
				"{\n"
				"	gl_FragColor = vColor * texture2D( unTexture, vTexCoord );\n"
				"}";

		static const char	fs_textured_alpha_test[] =
				"varying vec2 vTexCoord;\n"
				"\n"
				"uniform sampler2D unTexture;\n"
				"\n"
				"void main()\n"
				"{\n"
				"	gl_FragColor = texture2D( unTexture, vTexCoord );\n"
				"	if ( gl_FragColor.r < 0.1 ) discard;\n"
				"}";

		// vertex shaders
		static const char	vs_uni[] =
				"attribute vec2 inPosition;\n"
				"attribute vec2 inTexcoord;\n"
				"attribute vec4 inColor;\n"
				"\n"
				"varying vec4 vColor;\n"
				"varying vec2 vTexCoord;\n"
				"\n"
				"void main()\n"
				"{\n"
				"	gl_Position = vec4( inPosition, 0.0, 1.0 );\n"
				"	vColor = inColor;\n"
				"	vTexCoord = inTexcoord;\n"
				"}\n";


		ProgramPtr program = Program::New( ESS() );

		GLVertexAttribsInfo attribs;

		ShaderProgram & sp = program->GetShaderProgram();
		Shader  vs( gl_shader::VERTEX ),
				fs( gl_shader::FRAGMENT );

		const char *	vs_src = null;
		const char *	fs_src = null;

		int	in_position	= -1;
		int	in_texcoord	= -1;
		int	in_color	= -1;

		switch ( type )
		{
			case EDefaultShader::COLORED :
				vs_src		= vs_uni;
				fs_src		= fs_colored;
				in_position	= 0;
				in_color	= 1;
				break;

			case EDefaultShader::COLOR_WITH_TEXCOORDS :
				vs_src		= vs_uni;
				fs_src		= fs_colored;
				in_position	= 0;
				in_texcoord	= 1;
				in_color	= 2;
				break;

			case EDefaultShader::TEXTURED :
				vs_src		= vs_uni;
				fs_src		= fs_textured;
				in_position	= 0;
				in_texcoord	= 1;
				break;

			case EDefaultShader::TEXTURED_ALPHA_TEST :
				vs_src		= vs_uni;
				fs_src		= fs_textured_alpha_test;
				in_position	= 0;
				in_texcoord	= 1;
				break;

			case EDefaultShader::COLORED_WITH_OPACITY_MAP :
				vs_src		= vs_uni;
				fs_src		= fs_colored_with_opacity_map;
				in_position	= 0;
				in_texcoord	= 1;
				in_color	= 2;
				break;

			case EDefaultShader::COLORED_BLEND_WITH_TEXTURE :
				vs_src		= vs_uni;
				fs_src		= fs_colored_blend_with_texture;
				in_position	= 0;
				in_texcoord	= 1;
				in_color	= 2;
				break;

			default:
				RETURN_ERR( "unknown default shader type", void() );
		}
		
		CHECK_ERR( CreateShader( fs, fs_src ), void() );
		CHECK_ERR( CreateShader( vs, vs_src ), void() );

		sp.Attach( vs );
		sp.Attach( fs );

		if ( in_position >= 0 )
		{
			sp.BindAttrib( "inPosition", in_position );
			attribs.Set( in_position, GLVertexAttribsInfo::GLAttribInfo( gl_vertex_attrib::VEC2, in_position ) );
		}
		
		if ( in_texcoord >= 0 )
		{
			sp.BindAttrib( "inTexcoord", in_texcoord );
			attribs.Set( in_texcoord, GLVertexAttribsInfo::GLAttribInfo( gl_vertex_attrib::VEC2, in_texcoord ) );
		}
		
		if ( in_color >= 0 )
		{
			sp.BindAttrib( "inColor", in_color );
			attribs.Set( in_color, GLVertexAttribsInfo::GLAttribInfo( gl_vertex_attrib::VEC4, in_color ) );
		}

		sp.Link();
		sp.ShowLog();

		GLVertexAttribsInfo	attr_info;
		Primitives::Vertex2D::GetAttribs().GetAttribInfo( attr_info );

		SetProgramAttribs( program, attr_info );
		
		if ( type == EDefaultShader::TEXTURED or
			 type == EDefaultShader::COLORED_WITH_OPACITY_MAP or
			 type == EDefaultShader::COLORED_BLEND_WITH_TEXTURE )
		{
			program->AddTexture( "unTexture" );
		}
		
		program->_attribs = attribs;

		_defaultPrograms[ type ] = program;
	}


}	// Graphics
}	// Engine